/*
 * Copyright (C) 2019 ByteDance Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.bytedance.scene.animation;

import com.bytedance.scene.animation.interaction.scenetransition.SceneTransition;
import com.bytedance.scene.animation.interaction.scenetransition.utils.SceneViewCompatUtils;
import com.bytedance.scene.animation.interaction.scenetransition.visiblity.BackgroundFade;
import com.bytedance.scene.animation.interaction.scenetransition.visiblity.SceneVisibilityTransition;
import com.bytedance.scene.utlity.CancellationSignal;
import com.bytedance.scene.utlity.NonNullPair;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;

import java.util.*;

/**
 * Created by JiangQi on 9/2/18.
 */
public class SharedElementViewTransitionExecutor {
    private static final int ANIMATION_DURATION = 300;

    private final Map<String, SceneTransition> mSharedElementTransition;

    private final SceneVisibilityTransition mOtherTransition;

    /**
     * TODO: Support for excluding certain Views to do otherElementTransition
     */
    public SharedElementViewTransitionExecutor(Map<String, SceneTransition> sharedElementTransition,
                                               SceneVisibilityTransition otherElementTransition) {
        this.mSharedElementTransition = sharedElementTransition;
        this.mOtherTransition = otherElementTransition;
    }

    private static class Info {

        Component srcView;

        Component dstView;

        SceneTransition sceneTransition;

        private Info(Component srcView, Component dstView, SceneTransition sceneTransition) {
            this.srcView = srcView;
            this.dstView = dstView;
            this.sceneTransition = sceneTransition;
        }
    }

    public void executePushChange(final Component fromView, final Component toView,
                                  final Runnable endAction, CancellationSignal cancellationSignal,
                                  SharedElementNotFoundPolicy sharedElementNotFoundPolicy,
                                  Runnable fallbackAction) {
        Set<String> keySet = this.mSharedElementTransition.keySet();
        // Sort, don't let parent cover child
        HashMap<String, Component> map = new HashMap<>();
        for (String transitionName : keySet) {
            Component dstView = SharedElementUtils.getViewByTransitionName(toView, transitionName, true);
            if (dstView == null) {
                switch (sharedElementNotFoundPolicy) {
                    case ABORT:
                        throw new IllegalArgumentException("cant find " + transitionName + " View");
                    case FALLBACK:
                        fallbackAction.run();
                        return;
                }
            }
            map.put(transitionName, dstView);
        }

        List<NonNullPair<String, Component>> list = SharedElementUtils.sortSharedElementList(map);
        final List<Info> infoList = new ArrayList<>();
        List<Component> sharedElementViewList = new ArrayList<>();
        for (int i = 0; i < list.size(); i++) {
            String transitionName = list.get(i).first;

            SceneTransition sceneTransition = this.mSharedElementTransition.get(transitionName);
            Component srcView = SharedElementUtils.getViewByTransitionName(fromView, transitionName, true);
            Component dstView = list.get(i).second;

            if (srcView == null) {
                switch (sharedElementNotFoundPolicy) {
                    case ABORT:
                        throw new IllegalArgumentException("cant find " + transitionName + " View");
                    case FALLBACK:
                        fallbackAction.run();
                        return;
                }
            }

            infoList.add(new Info(srcView, dstView, sceneTransition));
            sharedElementViewList.add(dstView);
        }

        final List<Animator> animationList = new ArrayList<>();
        for (Info info : infoList) {
            info.sceneTransition.captureValue(info.srcView, info.dstView, info.dstView);
            SharedElementUtils.moveViewToOverlay(info.dstView, (ComponentContainer) toView, null);
            animationList.add(info.sceneTransition.getAnimator(true));
            info.srcView.setVisibility(Component.INVISIBLE);
        }

        List<Component> transitioningViews = SharedElementUtils.captureTransitioningViews(toView, toView);
        transitioningViews = SharedElementUtils.stripOffscreenViews(transitioningViews).first;
        transitioningViews.removeAll(sharedElementViewList);

        if (mOtherTransition != null) {
            List<Animator> mOtherAnimatorList = new ArrayList<>();
            for (Component view : transitioningViews) {
                // TODO: What if it is a set operation?
                SceneVisibilityTransition transition = mOtherTransition.clone();
                transition.setDuration(ANIMATION_DURATION);
                transition.captureValue(view, (ComponentContainer) toView);
                mOtherAnimatorList.add(transition.getAnimator(true));
            }

            long minDelay = Long.MAX_VALUE;
            for (Animator animator : mOtherAnimatorList) {
                minDelay = Math.min(minDelay, ((AnimatorValue) animator).getDelay());
            }

            // Guarantee that there must be at least one animator that is 0 delay
            for (Animator animator : mOtherAnimatorList) {
                ((AnimatorValue) animator).setDelay(animator.getDelay() - minDelay);
            }
            animationList.addAll(mOtherAnimatorList);
        }

        if (toView.getBackgroundElement() != null) {
            BackgroundFade backgroundFade = new BackgroundFade();
            backgroundFade.captureValue(toView, (ComponentContainer) toView);
            animationList.add(backgroundFade.getAnimator(true));
        }

        final AnimatorValue valueAnimator = (AnimatorValue) TransitionUtils.mergeAnimators(animationList);
        valueAnimator.setDuration(ANIMATION_DURATION);
        valueAnimator.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {
                SceneViewCompatUtils.suppressLayout((ComponentContainer) toView, true);
            }

            @Override
            public void onStop(Animator animator) {

            }

            @Override
            public void onCancel(Animator animator) {

            }

            @Override
            public void onEnd(Animator animator) {
                for (Info info : infoList) {
                    info.sceneTransition.finish(true);
                    SharedElementUtils.moveViewFromOverlay(info.dstView);
                    info.dstView.setVisibility(Component.VISIBLE);
                    info.srcView.setVisibility(Component.VISIBLE);
                }
                SceneViewCompatUtils.suppressLayout((ComponentContainer) toView, false);
                endAction.run();
            }

            @Override
            public void onPause(Animator animator) {

            }

            @Override
            public void onResume(Animator animator) {

            }
        });
        valueAnimator.start();
        cancellationSignal.setOnCancelListener(new CancellationSignal.OnCancelListener() {
            @Override
            public void onCancel() {
                valueAnimator.end();
            }
        });
    }

    public void executePopChange(final Component fromView, final Component toView,
                                 final Runnable endAction, CancellationSignal cancellationSignal,
                                 SharedElementNotFoundPolicy sharedElementNotFoundPolicy,
                                 Runnable fallbackAction) {
        final List<Info> infoList = new ArrayList<>();
        List<Component> sharedElementViewList = new ArrayList<>();
        Set<String> keySet = this.mSharedElementTransition.keySet();
        HashMap<String, Component> map = new HashMap<>();
        for (String transitionName : keySet) {
            Component srcView = SharedElementUtils.getViewByTransitionName(fromView, transitionName, true);
            if (srcView == null) {
                switch (sharedElementNotFoundPolicy) {
                    case ABORT:
                        throw new IllegalArgumentException("cant find " + transitionName + " View");
                    case FALLBACK:
                        fallbackAction.run();
                        return;

                }
            }
            map.put(transitionName, srcView);
        }

        List<NonNullPair<String, Component>> list = SharedElementUtils.sortSharedElementList(map);
        for (NonNullPair<String, Component> pair : list) {
            SceneTransition sceneTransition = this.mSharedElementTransition.get(pair.first);
            Component srcView = pair.second;
            Component dstView = SharedElementUtils.getViewByTransitionName(toView, pair.first, false);

            if (dstView == null) {
                switch (sharedElementNotFoundPolicy) {
                    case ABORT:
                        throw new IllegalArgumentException("cant find " + pair.first + " View");
                    case FALLBACK:
                        fallbackAction.run();
                        return;
                }
            }

            infoList.add(new Info(srcView, dstView, sceneTransition));
            sharedElementViewList.add(srcView);
        }

        final List<Animator> animationList = new ArrayList<>();
        for (Info info : infoList) {
            info.sceneTransition.captureValue(info.srcView, info.dstView, info.srcView);
            SharedElementUtils.moveViewToOverlay(info.srcView, (ComponentContainer) fromView, null);
            animationList.add(info.sceneTransition.getAnimator(false));
            info.dstView.setVisibility(Component.INVISIBLE);
        }

        List<Component> transitioningViews = SharedElementUtils.captureTransitioningViews(fromView, fromView);
        transitioningViews = SharedElementUtils.stripOffscreenViews(transitioningViews).first;
        transitioningViews.removeAll(sharedElementViewList);

        if (mOtherTransition != null) {
            List<Animator> mOtherAnimatorList = new ArrayList<>();
            for (Component view : transitioningViews) {
                SceneVisibilityTransition transition = mOtherTransition.clone();
                transition.setDuration(ANIMATION_DURATION);
                transition.captureValue(view, (ComponentContainer) fromView);
                mOtherAnimatorList.add(transition.getAnimator(false));
            }

            long minDelay = Long.MAX_VALUE;
            for (Animator animator : mOtherAnimatorList) {
                minDelay = Math.min(minDelay, ((AnimatorValue) animator).getDelay());
            }

            // Guarantee that there must be at least one animator that is 0 delay
            for (Animator animator : mOtherAnimatorList) {
                ((AnimatorValue) animator).setDelay(animator.getDelay() - minDelay);
            }
            animationList.addAll(mOtherAnimatorList);
        }

        if (fromView.getBackgroundElement() != null) {
            BackgroundFade backgroundFade = new BackgroundFade();
            backgroundFade.captureValue(fromView, (ComponentContainer) fromView);
            animationList.add(backgroundFade.getAnimator(false));
        }

        final AnimatorValue valueAnimator = (AnimatorValue) TransitionUtils.mergeAnimators(animationList);
        valueAnimator.setDuration(ANIMATION_DURATION);
        valueAnimator.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {
                SceneViewCompatUtils.suppressLayout((ComponentContainer) toView, true);
            }

            @Override
            public void onStop(Animator animator) {

            }

            @Override
            public void onCancel(Animator animator) {

            }

            @Override
            public void onEnd(Animator animator) {
                for (Info info : infoList) {
                    info.sceneTransition.finish(false);
                    SharedElementUtils.moveViewFromOverlay(info.srcView);
                    info.dstView.setVisibility(Component.VISIBLE);
                    info.srcView.setVisibility(Component.VISIBLE);
                }
                SceneViewCompatUtils.suppressLayout((ComponentContainer) toView, false);
                endAction.run();
            }

            @Override
            public void onPause(Animator animator) {

            }

            @Override
            public void onResume(Animator animator) {

            }
        });
        valueAnimator.start();
        cancellationSignal.setOnCancelListener(new CancellationSignal.OnCancelListener() {
            @Override
            public void onCancel() {
                valueAnimator.end();
            }
        });
    }
}
